// scaup - Scala up!
// (c) 2008-2009, Normen Müller, normen.mueller@gmail.com  
// $Id$
package locutor.model.grgen.parsing

import java.io.File
import java.text.MessageFormat.{format}

import scaup.Views._

/** 
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.2 */
case class GRGError(message: String) extends Exception(message)

/** This object provides a simple interface to the GRG parser class.
 * 
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.2 */
object GRG extends Parser {
  def apply(input: File): Any =
    if(input == null || !(input exists))
      throw new GRGError(format("Physical GRG file may not be null (''{0}'').", input))
    else apply(input.content)
  
  def apply(input: String) =
    if(input == null)
      throw new GRGError("Physical GRG representation may not be null.")
    else parse(input)
  
  def parse(input: String) = phrase(grg)(new lexical.Scanner(input)) match {
    case Success(result, _) => result
    case n@_                => throw new GRGError(format("GRG parsing error in ''{0} ...}''\n", input.lines.next) + n)
  }
}

import scala.util.parsing.combinator._
import scala.util.parsing.combinator.lexical._
import scala.util.parsing.input.CharArrayReader.EofCh

/** 
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.2 */
private [grgen] class Lexer extends StdLexical with RegexParsers {
  override type Elem = Char
  
  override def token: Parser[Token] = 
    ( string                             ^^ StringLit
    | number                             ^^ NumericLit
    | delim
    | EofCh                             ^^^ EOF
    | id                                 ^^ {case i if reserved contains i => Keyword(i) case i => Identifier(i)}        
    | '\"' ~> failure("Unterminated string")
    | failure("Illegal character")
    )
  
  override def whitespace: Parser[Any] = rep(whitespaceChar | "\\" | '#' ~ rep(chrExcept(EofCh, '\n')))
  
  def string  =      
    ( '\"' ~> rep(charSeq | chrExcept('\"', '\n', EofCh)) <~ '\"' ^^ { _ mkString "" }
    | '\'' ~> rep(charSeq | chrExcept('\'', '\n', EofCh)) <~ '\'' ^^ { _ mkString "" }    
    )
  
  def number  = """-?(\d+(\.\d*)?|\d*\.\d+)([eE][+-]?\d+)?[fFdD]?""".r
  
  def id      = rep(letter | digit | elem("underscore", _=='_')) ^^ { _ mkString "" }
  
  def charSeq = 
    ( '\\' ~ '\"' ^^^ "\"" | '\\' ~ '\\' ^^^ "\\" | '\\' ~ '/'  ^^^ "/" | '\\' ~ 'b'  ^^^ "\b" | '\\' ~ 'f'  ^^^ "\f" 
    | '\\' ~ 'n'  ^^^ "\n" |'\\' ~ 'r'  ^^^ "\r" |'\\' ~ 't'  ^^^ "\t" |'\\' ~ 'u' ~> unicodeBlock
    )
  
  import scala.io.UTF8Codec.{encode}
  def unicodeBlock = hexDigit ~ hexDigit ~ hexDigit ~ hexDigit ^^ {
    case a ~ b ~ c ~ d => new String(encode(Integer.parseInt(List(a, b, c, d) mkString "", 16)))
  }
  
  val hexDigits = Set[Char]() ++ "0123456789abcdefABCDEF".toArray
  
  def hexDigit = elem("hex digit", hexDigits.contains(_))
}

/** 
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.2 */
private [grgen] class Parser extends ExprParser {
  /* Fill in abstract defs*/
  type Tokens = Lexer
  val lexical: Tokens = new Lexer
  
  /* Configure lexical parsing */                                 
  lexical.reserved ++= List(
    "using", "test", "exact", "induced", "rule", "dpo", "hom", "negative", "if", "return",
    "replace", "modify", "delete", "eval", "emit", "exec"
  )
  
  lexical.delimiters ++= List(
    "{", "}",
    "(", ")",
    "[", "]",
    "<-", "->", "--", "<->", "?-", "-?",
    ";", ".", ",", "=", ":", "::", "\\",
    "?", "!",
    "+", "-", "*", "/", "%",
    "^", "&&", "||", "&", "|",
    "<=", "<<", "<", "==", "!=", ">=", ">>>", ">>", ">"     
  )
  
  /* Define the grammar */
  def grg = string
  
  def graphlet = ((graphlet_node ~ opt(continuation)) | continuation) ~ ";"
  
  def graphlet_node = (ident | "." | opt(ident) ~ ":" ~ ident ~ opt(type_constraint | "<" ~ ident ~ ">"))
  
  def graphlet_edge =
    ( "-" ~ opt(edge_refinement) ~ "->"
    | "<-" ~ opt(edge_refinement) ~ "-"
    | "<-" ~ opt(edge_refinement) ~ "->"
    | "?-" ~ opt(edge_refinement) ~ "-?"
    )
  
  def edge_refinement = (ident | opt(ident) ~ ":" ~ ident ~ opt(type_constraint | "<" ~ ident ~ ">"))
  
  def rule_set = opt("using" ~ repsep(ident, ",") ~ ";") ~ ((test_decl | rule_decl)+)
  
  def test_decl = "test" ~ opt("exact" | "induced") ~ action_sig ~ "{" ~ pattern ~ "}"
  
  def rule_decl = "rule" ~ opt("exact" | "induced" | "dpo") ~ action_sig ~ "{" ~ pattern ~ replace ~ "}"
  
  def action_sig = ident ~ opt(params ~ opt(":" ~ return_types))
  
  def params = "(" ~ repsep(((ident ~ ":" ~ ident) | "-" ~ ident ~ ":" ~ ident ~ "->"), ",") ~ ")"
  
  def return_types = "(" ~ repsep(ident, ",") ~ ")"
  
  def type_constraint = "\\" ~ "(" ~ repsep(type_expr, "+") ~ ")"
  
  def pattern = ((pattern_stmt)*) ~ opt(return_stmt)
  
  def pattern_stmt: Parser[Any] = 
    ( graphlet ~ ";"
    | "hom" ~ "(" ~ repsep(ident, ",") ~ ")" ~ ";"
    | "negative" ~ "{" ~ opt(pattern_stmt) ~ "}"
    | "if" ~ "{" ~ repsep(bool_expr, ";") ~ "}"
    | ("exact" | "induced") ~ "(" ~ repsep(ident, ",") ~ ")"
    ) 
  
  def return_stmt = "return" ~ "(" ~ repsep(ident, ",") ~ ")" ~ ";"
  
  def replace = ("replace" | "modify") ~ "{" ~ ((replace_stmt)*) ~ opt(return_stmt) ~ opt(exec_stmt) ~ "}"
  
  def replace_stmt =
    ( graphlet ~ ";"
    | "delete" ~ "(" ~ repsep(ident, ",") ~ ")" ~ ";"
    | "eval" ~ "{" ~ repsep(assignment, ";") ~ "}"
    )
  
  def assignment = ident ~ "." ~ ident ~ "=" ~ expr
  
  def exec_stmt =
    ( "emit" ~ "(" ~ string_expr ~ ")" ~ ";"
    | "exec" ~ "(" ~ rewrite_seq ~ ")" ~ ";"
    )
  
  def rewrite_seq: Parser[Any] =
    opt("!") ~ rewrite_term ~ opt("*" | "+" | "[" ~ (numeric ~ opt(":" ~ numeric)) ~ "]") ~
    (( opt("$") ~ "&&" ~ rewrite_seq
     | opt("$") ~ "||" ~ rewrite_seq
     | opt("$") ~ "&" ~ rewrite_seq
     | opt("$") ~ "|" ~ rewrite_seq
    )*)
  
  def rewrite_term =
    ( rule_exec
    | var_assign
    | "<" ~ rewrite_seq ~ ">"
    | "(" ~ rewrite_seq ~ ")"
    | ident
    | "def" ~ "(" ~ vars ~ ")"
    | "true"
    | "false"
    )
  
  def vars       = repsep(ident, ",")
  def rule       = ident ~ opt("(" ~ vars ~ ")")   
  def var_assign = ident ~ "=" ~ (text | numeric | "(" ~ rewrite_seq ~ ")" | "true" | "false")
  def rule_exec  = opt("(" ~ vars ~ ")" ~ "=") ~ (("[" ~ opt("%") ~ opt("?") ~ rule ~ "]") | rule)  
  
  def continuation: Parser[Any] = graphlet_edge ~ opt(graphlet_node ~ opt(continuation))
}

